// 手写 call, apply, bind
Function.prototype.myCall = function (context) {
    if (typeof this !== 'function') { // 调用类型判断
        console.error('type error');
    }
    let args = [...arguments].slice(1), // 获取形参
        result = null;
    context = context || window; // 没有指明this，默认指向window
    context.fn = this; // 将函数设为对象的方法
    result = context.fn(...args); // 调用函数
    delete context.fn; // 删除属性
    return result; // 返回结果
}

Function.prototype.myApply = function (context) {
    if (typeof this !== 'function') { // 调用类型判断
        throw new TypeError('Error');
    }
    let result = null;
    context = context || window;
    context.fn = this;
    if (arguments[1]) { // 判断是否传入参数
        result = context.fn(...arguments[1])
    } else {
        result = context.fn();
    }
    delete context.fn;
    return result;
}

Function.prototype.myBind = function (context) {
    if (typeof this !== 'function') {
        throw new TypeError('error');
    }
    let args = [...arguments].slice(1),
        fn = this;
    return function Fn() {
        return fn.apply(this instanceof Fn ? this : context, args.concat(...arguments));
    }
}

// instanceof 判断(右边)构造函数的 prototype 属性是否在 (左边)对象的原型链上
// 手写
function myInstanceof(left, right) {
    let proto = Object.getPrototypeOf(left),
        prototype = right.prototype;
    while (true) {
        if (!proto) return false;
        if (proto === prototype) return true;
        proto = Object.getPrototypeOf(proto);
    }
}

// 尾调用实现斐波那契
function fabnaci(n, x1 = 0, x2 = 1) {
    if (n === 1) return x1;
    return fabnaci(n - 1, x2, x1 + x2);
}

// 数组扁平化
const arr = [1, [2, [3, [4, 5]]], 6, '7']
// 1. arr.flat(Infinity) ES6
// 2. 使用正则搭配 JSON 的两个函数，缺点：对数组中的 function,Symbol类型无法转换
let res = JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') + ']')
// 3. reduce
const flatten = arr => {
    return arr.reduce((pre, cur) => {
        return pre.concat(Array.isArray(cur) ? flatten(cur) : cur)
    }, [])
}
// 递归
const resCopy = []
const fn = arr => {
    for (let i = 0; i < arr.length; i++) {
        if (Array.isArray(arr[i])) {
            fn(arr[i])
        } else {
            resCopy.push(arr[i])
        }
    }
    return resCopy
}


// 栈和堆
// 栈区内存由编译器自动分配释放，存放函数的参数值，局部变量的值等。其操作方式类似于数据结构中的栈。
// 堆区内存一般由程序员分配释放，若程序员不释放，程序结束时可能由垃圾回收机制回收。
// 申请效率的比较：
//      栈由系统自动分配，速度较快。但程序员是无法控制的。
//      堆是由new分配的内存，一般速度比较慢，而且容易产生内存碎片,不过用起来最方便。

// 快速排序


// 事件循环机制
/*
因为 js 是单线程运行的，在代码执行的时候，通过将不同函数的执行上下文压入执行栈中来保证代码
的有序执行。在执行同步代码的时候，如果遇到了异步事件，js 引擎并不会一直等待其返回结果， 
而是会将这个事件挂起，继续执行执行栈中的其他任务。当异步事件执行完毕后，再将异步事件对应的
回调加入到与当前执行栈中不同的另一个任务队列中等待执行。任务队列可以分为宏任务对列和
微任务对列，当当前执行栈中的事件执行完毕后，js 引擎首先会判断微任务对列中是否有任务可以执
行，如果有就将微任务队首的事件压入栈中执行。当微任务对列中的任务都执行完成后再去判断宏任务对列中的任务。
微任务包括了 promise 的回调、node 中的 process.nextTick、对 Dom 变化监听 MutationObserver。
宏任务包括了 script 脚本的执行、setTimeout，setInterval，setImmediate 一类的定时
事件，还有如 I/O 操作、UI 渲染等。
*/

// vue 组件传值
/* 
    props 
        子组件通过 props 属性接收父组件的数据；
        父组件在子组件注册监听事件，子组件通过 emit 来向父组件发送数据
    ref
        给子组件设置一个名称，父组件通过 $refs 组件名来获取子组件
        子组件通过 $parent 获取父组件
    provider/inject
    eventBus
        创建一个事件中心（空的 Vue 实例作为消息传递的对象），相当于中转站，用来传递事件和接收事件
    vuex
        处理公共数据 

*/

// vue 生命周期
/*
    创建前：beforeCreate，实例初始化后，在数据监听和事件配置前触发，所以获取不到 data
    创建后：created，实例创建完后触发，可以访问 data,methods 等属性，组件还未挂载到页面中去，访问不到 $el 属性，可以进行页面初始化工作，请求数据
    挂载前：beforeMount，组件挂载到页面前触发，找到 template 模板并编译成 render 函数
    挂载后：mounted，组件挂载到页面上触发，可以获取到 DOM 元素
    更新前：beforeUpdate，在响应式数据更新时触发，虚拟 DOM 重新渲染和打补丁之前，对可能会被移除的元素做些操作（移除事件监听器）
    更新后：updated，虚拟 DOM 重新渲染和打补丁之后调用
    销毁前：beforeDestory，实例销毁前，销毁定时器，解绑全局事件
    销毁后：destoryed，实例销毁后触发

    keep-alive：保存组件状态，防止多次渲染
        activated：缓存渲染后执行
        deactivated：组件切换时，缓存到内存中时执行
*/

// watch,computed
/* 
    watch: 监听以及挂载到 Vue 实例上的数据，数据发生变化便会调用执行函数，适用：一个数据影响多个数据
    computed: 一个计算属性，并挂载到 Vue 实例上，惰性求值，依赖项发生变化时才求值，适用：一个数据被多个数据影响

*/